#include <QtGui>
#include <QtOpenGL>
#include <math.h>
#include <QSettings>
#include "glwidget.h"
#include <QFile>
#include <QFileDialog>
#include "tga.h"

#define HEIGHTMAP 1024 * 1024
#define MAP_SIZE 1024
#define STEP_SIZE 16


unsigned char heightMap[HEIGHTMAP];
float angle=0.0,deltaAngle = 0.0;
GLfloat camX,camY,camZ;
float lx=0.0f,ly=0.0f,lz=-1.0f;
int deltaMove = 0,deltaUp=0;
GLuint heightmap_texture;
GLuint sky_texture;
GLuint tree_texture;
GLuint particle_texture;
GLfloat scaleValue = 0.1f;
float skyMovCounter = 0.0f;
GLuint terrainList;
GLuint skyList;
GLuint treeList;
float lightAmbient[] = { 1.0f, 1.0f, 1.0f, 1.0f };
float lightDiffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
float lightPosition[] = { 1000.0f, 1000.0f, -1000.0f, 1.0f };
GLUquadric* quadric;

GLWidget::GLWidget(QWidget *parent): QGLWidget(parent)
{
    renderType = TEXTURED;
    showMap=true;
    showSky=true;
    density = 0.05; //set the density to 0.3 which isacctually quite thick

    fogColor[0] = 0.5;
    fogColor[1] = 0.5;
    fogColor[2] = 0.5;
    fogColor[3] = 0.5;

    loadSettings();
    saveSettings();


}
GLWidget::~GLWidget()
{
    //delete heightMap;
}

void GLWidget::initializeGL()
{

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glClearDepth(1.0f); // Depth Buffer Setup
    glEnable(GL_DEPTH_TEST); // Enables Depth Testing
    glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
    glShadeModel(GL_SMOOTH); // try setting this to GL_FLAT and see what happens
    glEnable(GL_MULTISAMPLE);
    glEnable(GL_NORMALIZE);
    // Really Nice Perspective Calculations
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    initHeightMap();
    initHeightMapTexture();
    genHeightMapList();
    initSkyTexture();
    initTrees();

    camX = 700;
    camZ = 700;
    camY = height(heightMap,camX,camZ)+20;

    setLightning();

    quadric = gluNewQuadric();
    gluQuadricNormals(quadric, GLU_SMOOTH);
    gluQuadricTexture(quadric, true);
    drawSky();

    setFog(showFog);

    glFogi (GL_FOG_MODE, GL_EXP2); //set the fog mode to GL_EXP2

    glFogfv (GL_FOG_COLOR, fogColor); //set the fog color to our color chosen above
    glFogf (GL_FOG_DENSITY, density); //set the density to the  value above
    glHint (GL_FOG_HINT, GL_NICEST); // set the fog to look the
}

void GLWidget::paintGL()
{

    if (deltaMove)
        moveMeFlat(deltaMove);
    if (deltaUp)
        Lookup(deltaUp);
    if (deltaAngle) {
        angle += deltaAngle;
        orientMe(angle);
    }
    GLfloat cam[3];
    cam[0] = camX;
    cam[1] = camY;
    cam[2] = camZ;

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity();

    gluLookAt(camX, camY, camZ, camX + lx,camY + ly,camZ + lz,0.0f,1.0f,0.0f);

    setLightning();
    if(showFog)
        glEnable(GL_FOG);
    else
        glDisable(GL_FOG);

    glFogf (GL_FOG_DENSITY, density);

    enableCullingMode(true);

    glEnable(GL_LIGHTING);
    glBindTexture(GL_TEXTURE_2D, heightmap_texture);
    if(showMap)glCallList(terrainList);


    glDisable(GL_LIGHTING);
    glBindTexture(GL_TEXTURE_2D, sky_texture);
    if(showSky)drawSky();


    glEnable(GL_LIGHTING);
    if(showMap)drawTrees();

    swapBuffers();
}

void GLWidget::getRightVector(float *right) {

        float modelview[16];

        glGetFloatv(GL_MODELVIEW_MATRIX, modelview);

        right[0] = modelview[0];
        right[1] = modelview[4];
        right[2] = modelview[8];
}


void GLWidget::drawTrees(){


    float pos[3],right[3],up[3];
    glPushMatrix();

    srand(0);
    enableCullingMode(false);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0);
    glBindTexture(GL_TEXTURE_2D, tree_texture);


    for(int i = -10; i < 10; i+=2)
        for(int j = -10; j < 10; j+=2) {
        glPushMatrix();


        double randPos = ((rand() / static_cast<double>( RAND_MAX )) * 100) / 2;
        glTranslatef(500+i*randPos,height(heightMap,500+i*randPos,500+j * randPos)-1,500+j * randPos);

        up[0] = 0;up[1] = 1;up[2]=0;

        getRightVector(right);
        pos[0] = 500+i*50.0; pos[1] = 0; pos[2] = 500+j * 50.0;

        glBegin(GL_QUADS);
        glTexCoord2f(0,0);glVertex3f(-20.0f * right[0],             0.0f,                          -20.0 * right[2]);
        glTexCoord2f(1,0);glVertex3f( 20.0f * right[0],             0.0f,			    20.0 * right[2]);
        glTexCoord2f(1,1);glVertex3f( 20.0f * right[0] + 40 * up[0], 40.0f * up[1] + 20 * right[1], 20.0 * right[2] + 40 * up[2]);
        glTexCoord2f(0,1);glVertex3f(-20.0f * right[0] + 40 * up[0], 40.0f * up[1] - 20 * right[1],-20.0 * right[2] + 40 * up[2]);
        glEnd();

        glPopMatrix();

    }


    glDisable(GL_BLEND);
    glDisable(GL_ALPHA_TEST);



}

void GLWidget::orientMe(float ang) {
    lx = sin(ang);
    lz = -cos(ang);
}


void GLWidget::moveMeFlat(int i) {
    camX = camX + i*(lx)*0.1;
    camY = height(heightMap,camX,camZ) + 20 +  i*(ly)*0.1;
    camZ = camZ + i*(lz)*0.1;
}

void GLWidget::Lookup(int i) {
    ly +=0.001*i;
}


void GLWidget::resizeGL(int w, int h)
{

    h = (h == 0) ? 1 : h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, (float) w / h, 1, 10000); // near plane 10000
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

}

void GLWidget::onTimer()
{
    updateGL();
}


void GLWidget::enableCullingMode(bool value) {
    if (value) {
        glCullFace(GL_BACK);
        glEnable(GL_CULL_FACE);
        glFrontFace(GL_CCW);
    } else
        glDisable(GL_CULL_FACE);
}

void GLWidget::setLightning() {
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
}

void GLWidget::initSkyTexture(bool msg){

    if (!QFile::exists(skyTex))
    {
        if(msg){
            QMessageBox::warning(this, tr("::GLWidget skyTex"),tr("File not found:") + skyTex);
        }
        skyTex = QFileDialog::getOpenFileName(this, tr("Open Sky Texture Image"), "", tr("Image Files (*.bmp)"));
        saveSettings();
    }

    glGenTextures(1, &sky_texture);
    sky_texture =  bindTexture(QPixmap(skyTex));
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

}

void GLWidget::drawSky() {

    glEnable(GL_TEXTURE_2D);

    enableCullingMode(false);

    glPushMatrix();
    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    glRotatef(skyMovCounter, 0.0f, 0.0f, 1.0f);
    glTranslatef(camX + 500, camY, camZ - MAP_SIZE
                 * scaleValue * 0.5f);
    double clipPlane1[] = { 0.0f, 0.0f, 1.0f, 0.5f };
    glClipPlane(GL_CLIP_PLANE1, clipPlane1);
    glEnable(GL_CLIP_PLANE1);

    gluSphere(quadric, 5000, 50, 5);
    glDisable(GL_CLIP_PLANE1);
    glPopMatrix();


    glPushMatrix();
    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);

    glTranslatef(camX + 500, camY, camZ - MAP_SIZE
                 * scaleValue * 0.5f);
    double clipPlane2[] = { 0.0f, 0.0f, -1.0f, 0.5f };
    glClipPlane(GL_CLIP_PLANE2, clipPlane2);
    glEnable(GL_CLIP_PLANE2);
    gluSphere(quadric, 5000, 50, 5);
    glDisable(GL_CLIP_PLANE2);
    glPopMatrix();

}

int GLWidget::height(unsigned char* pHeightMap, int X, int Y) {
    int x = X % MAP_SIZE;
    int y = Y % MAP_SIZE;

    return pHeightMap[x + (y * MAP_SIZE)] & 0xFF;
}

void GLWidget::setVertexColor(unsigned char* pHeightMap, int x, int y) {
    int h = height(pHeightMap, x, y);

    if (h >= 250)
        glColor3f(0.984313f, 0.3019607f, 0.3019607f);
    else if (h > 200)
        glColor3f(1.0f, 0.5607843f, 0.243147f);
    else if (h > 150)
        glColor3f(0.50196f, 1.0f, 0.50196f);
    else if (h > 100)
        glColor3f(0.011764f, 0.55294117f, 0.1490196f);
    else if (h > 50)
        glColor3f(0.011764f, 0.6862745f, 0.945098f);
    else
        glColor3f(0.011764f, 0.270588f, 0.945098f);

}

void GLWidget::keyEv(QKeyEvent *e)
{
    switch( e->key() )
    {
    case Qt::Key_Escape:
        exit(0);
        break;
    case Qt::Key_W:
        deltaMove = 10;
        break;
    case Qt::Key_S:
        deltaMove = -10;
        break;
    case Qt::Key_A:
        deltaAngle = -0.01f;
        break;
    case Qt::Key_D:
        deltaAngle = 0.01f;
        break;
    default:
        QGLWidget::keyPressEvent( e );
    }
}

void GLWidget::keyReleaseEvent(QKeyEvent *e){
    switch( e->key() )
    {
    case Qt::Key_Escape:
        exit(0);
        break;
    case Qt::Key_W:

    case Qt::Key_S:
        deltaMove = 0;
        break;
    case Qt::Key_A:
        deltaAngle = 0;
        break;
    case Qt::Key_D:
        deltaAngle = 0;
        break;
    default:
        QGLWidget::keyPressEvent( e );
    }
}

void GLWidget::keyPressEvent(QKeyEvent * e)
{
    keyEv(e);
}

void GLWidget::enterEvent(QEvent *e){
    QGLWidget::enterEvent(e);
    setFocus();
    grabKeyboard();
}

void GLWidget::leaveEvent(QEvent *e){
    QGLWidget::leaveEvent(e);
    releaseKeyboard();
}

void GLWidget::loadSettings()
{
    QSettings set("settings.ini",QSettings::IniFormat);
    set.beginGroup("GLWidgetFiles");
    skyTex=set.value("skyTex","sky.bmp").toString();
    hMapTex=set.value("hMapTex","Heightfield.bmp").toString();
    hMapRaw=set.value("hMapRaw","Heightfield.raw").toString();
    treeTex = set.value("treeTex","tree.tga").toString();
    showMap=set.value("showMap","true").toBool();
    showSky=set.value("showSky","true").toBool();
    showFog=set.value("showFog","true").toBool();
    density=set.value("density","0.005").toDouble();
    set.endGroup();
}

void GLWidget::saveSettings()
{
    QSettings set("settings.ini",QSettings::IniFormat);
    set.beginGroup("GLWidgetFiles");
    set.setValue("skyTex",skyTex);
    set.setValue("hMapTex",hMapTex);
    set.setValue("hMapRaw",hMapRaw);
    set.setValue("treeTex",treeTex);;
    set.setValue("showMap",showMap);
    set.setValue("showSky",showSky);
    set.setValue("showFog",showFog);
    set.setValue("density",density);
    set.endGroup();
}

void GLWidget::initHeightMap(bool msg){
    if (!QFile::exists(hMapRaw))
    {
        if(msg){
            QMessageBox::warning(this, tr("::GLWidget hMapRaw"),tr("File not found: ") + hMapRaw);
        }
        hMapRaw=  QFileDialog::getOpenFileName(this, tr("Open Heightmap Raw Image"), "", tr("Raw Files (*.raw)"));
        saveSettings();
    }
    FILE *file;
    file = fopen(hMapRaw.toStdString().c_str(), "rb");
    fread(heightMap, 1, HEIGHTMAP, file);
    fclose(file);

    for (int i = 0; i < 1024 * 1024; i++) {
        heightMap[i] &= 0xFF;
    }
}

void GLWidget::initHeightMapTexture(bool msg){
    if (!QFile::exists(hMapTex))
    {
        if(msg){
            QMessageBox::warning(this, tr("::GLWidget hMapTex"), tr("File not found:") + hMapTex);
        }
        hMapTex = QFileDialog::getOpenFileName(this, tr("Open Heightmap Texture Image"), "", tr("Image Files (*.bmp)"));
        saveSettings();
    }

    glGenTextures(1, &heightmap_texture);
    heightmap_texture =  bindTexture(QPixmap(hMapTex));
    glBindTexture(GL_TEXTURE_2D, heightmap_texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void GLWidget::genHeightMapList()
{

    terrainList = glGenLists(1);
    glNewList(terrainList, GL_COMPILE);

    glBindTexture(GL_TEXTURE_2D, heightmap_texture);

    glBegin(GL_QUADS);

    for (int X = 0; X < (MAP_SIZE - STEP_SIZE); X += STEP_SIZE)
        for (int Y = 0; Y < (MAP_SIZE - STEP_SIZE); Y += STEP_SIZE) {

        int x = X;
        int y = height(heightMap, X, Y);
        int z = Y;
        if (renderType == TEXTURED)
            glTexCoord2f((float) x / (float) MAP_SIZE, (float) z
                         / (float) MAP_SIZE);
        else
            setVertexColor(heightMap, x, z);
        glVertex3i(x, y, z);
        x = X;
        y = height(heightMap, X, Y + STEP_SIZE);
        z = Y + STEP_SIZE;
        if (renderType == TEXTURED)
            glTexCoord2f((float) x / (float) MAP_SIZE, (float) (z + 1)
                         / (float) MAP_SIZE);
        else
            setVertexColor(heightMap, x, z);
        glVertex3i(x, y, z);

        x = X + STEP_SIZE;
        y = height(heightMap, X + STEP_SIZE, Y + STEP_SIZE);
        z = Y + STEP_SIZE;
        if (renderType == TEXTURED)
            glTexCoord2f((float) (x + 1) / (float) MAP_SIZE,
                         (float) (z + 1) / (float) MAP_SIZE);
        else
            setVertexColor(heightMap, x, z);
        glVertex3i(x, y, z);

        x = X + STEP_SIZE;
        y = height(heightMap, X + STEP_SIZE, Y);
        z = Y;
        if (renderType == TEXTURED)
            glTexCoord2f((float) (x + 1) / (float) MAP_SIZE, (float) z
                         / (float) MAP_SIZE);
        else
            setVertexColor(heightMap, x, z);
        glVertex3i(x, y, z);
    }

    glEnd();

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glEndList();
}

void GLWidget::initTrees(bool msg){

    if (!QFile::exists(treeTex))
    {
        if(msg){
            QMessageBox::warning(this, tr("::GLWidget particleTex"),tr("File not found:") + treeTex);
        }
        treeTex = QFileDialog::getOpenFileName(this, tr("Open Tree TGA Image"), "", tr("Image Files (*.tga)"));
        saveSettings();
    }

    tgaInfo *image;
    glEnable(GL_DEPTH_TEST);
    glGenTextures(1, &tree_texture);

    char* treeTexString = treeTex.toLatin1().data();
    image = tgaLoad(treeTexString);
    glBindTexture(GL_TEXTURE_2D,tree_texture);
    glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,	GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,	GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER	,GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER	,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, image->imageData);
    glEnable(GL_TEXTURE_2D);
}


void GLWidget::setHMapRaw()
{
    hMapRaw="";
    initHeightMap(false);
    genHeightMapList();
}

void GLWidget::setSkyTex()
{
    skyTex="";
    initSkyTexture(false);
}

void GLWidget::setHMapTex()
{
    hMapTex="";
    initHeightMapTexture(false);
    genHeightMapList();
}

QString GLWidget::getSkyTex()
{
    return skyTex;
}

QString GLWidget::getHMapTex()
{
    return hMapTex;
}

QString GLWidget::getHMapRaw()
{
    return hMapRaw;
}

void GLWidget::mousePressEvent(QMouseEvent *event)
{
    lastPos = event->pos();
}

void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
    int dx = event->x() - lastPos.x();
    int dy = event->y() - lastPos.y();


    if (event->buttons() & Qt::LeftButton) {
        Lookup(-dy);
    }
    lastPos = event->pos();
}

void GLWidget::mouseReleaseEvent(QMouseEvent  *event)
{

}

void GLWidget::setFog(bool show)
{
    showFog=show;
}

void GLWidget::setFogDensity(int dens)
{
    density=dens/1000.0;
    //  //set the density to the  value above
}
